package ru.papergames.web.blines.battle;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import ru.papergames.common.beans.AbstractPlayerInFightersPool;
import ru.papergames.common.beans.NpcPlayerInFightersPool;
import ru.papergames.common.beans.PcPlayerInFightersPool;
import ru.papergames.common.form.AbstractPlayerForm;
import ru.papergames.common.form.NpcPlayerForm;
import ru.papergames.common.form.PcPlayerForm;
import ru.papergames.web.blines.battle.model.BattleOrder2;
import ru.papergames.web.cache.PlayersManager;
import ru.papergames.web.npc.NpcsManager;

import java.util.*;

/**
 * @author Alexander Troshanin
 */
class BaseFightersPoolManagerThread extends Thread {

    private static Log logger = LogFactory.getLog(BaseFightersPoolManagerThread.class);

    private static final int WAIT_IN_POOL_TIMEOUT = 5 * 1000;

    private static final int BATTLE_PLAYERS_COUNT = 3;

    private static final Object LOCK = new Object();
    private volatile boolean shutdown;

    public void shutdown() {
        synchronized (LOCK) {
            shutdown = true;
            LOCK.notify();
        }
    }

    public void run() {

        while (!shutdown) {
            synchronized (LOCK) {
                if (shutdown) {
                    break;
                }
                try {
                    processFighterPool();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
                try {
                    LOCK.wait(3 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace(); // THIS might never occur...
                    shutdown = true;
                }
            }
        }

    }

    private void processFighterPool() {
        Set<Map.Entry<Long, PcPlayerInFightersPool>> players = PlayersManager.getInstance().getBaseFightersPool().entrySet();
        Map<Integer, List<PcPlayerInFightersPool>> playersByLevels = new HashMap<Integer, List<PcPlayerInFightersPool>>();
        for (Map.Entry<Long, PcPlayerInFightersPool> entry : players) {
            PcPlayerInFightersPool player = entry.getValue();
            List<PcPlayerInFightersPool> playersOfLevel = playersByLevels.get(player.getPlayer().getLevel());
            if (playersOfLevel == null) {
                playersOfLevel = new ArrayList<PcPlayerInFightersPool>();
                playersByLevels.put(player.getPlayer().getLevel(), playersOfLevel);
            }
            playersOfLevel.add(player);
        }
        List<List<AbstractPlayerInFightersPool>> commands = new ArrayList<List<AbstractPlayerInFightersPool>>();
        for (List<PcPlayerInFightersPool> playersOfLevel : playersByLevels.values()) {
            Collections.sort(playersOfLevel);

            List<AbstractPlayerInFightersPool> command = new ArrayList<AbstractPlayerInFightersPool>(BATTLE_PLAYERS_COUNT);
            for (PcPlayerInFightersPool playerInPool : playersOfLevel) {
                command.add(playerInPool);
                if (command.size() == BATTLE_PLAYERS_COUNT) {
                    commands.add(command);
                    command = new ArrayList<AbstractPlayerInFightersPool>(BATTLE_PLAYERS_COUNT);
                }
            }
            if (command.size() > 0) {
                // this command is not full so we need to check how long players are waiting.
                // TODO: check waiting time and add boots if necessary
                for (AbstractPlayerInFightersPool playerInPool : command) {
                    if (isWaitInPoolTimeout((PcPlayerInFightersPool) playerInPool)) {
                        logger.debug("Timeout occurred during player[" + playerInPool.getPlayer().getId() + "] wait in pool");
                        addNpcsInCommand(command);
                        commands.add(command);
                        break;
                    }
                }
            }
        }

        List<BattleOrder2> battleOrders = new ArrayList<BattleOrder2>(commands.size());
        for (List<AbstractPlayerInFightersPool> command : commands) {
            BattleOrder2 battleOrder = new BattleOrder2(command.size());
            int team = 0;
            for (AbstractPlayerInFightersPool playerInPool : command) {
                AbstractPlayerForm player = playerInPool.getPlayer();
                if (player instanceof PcPlayerForm) {
                    PlayersManager.getInstance().addPlayerInBattleOrder((PcPlayerForm) player, battleOrder, team);
                } else {
                    NpcsManager.getInstance().addNpcInBattleOrder((NpcPlayerForm) player, battleOrder, team);
                }
                team++;
            }
            battleOrders.add(battleOrder);
        }

        BattleOrdersHolder2.getInstance().addOrdersAndLaunch(battleOrders);
    }

    private void addNpcsInCommand(List<AbstractPlayerInFightersPool> command) {
        Collection<NpcPlayerForm> npcPlayers = NpcsManager.getInstance().getFreeNpcsSet();
        for (NpcPlayerForm npcPlayer : npcPlayers) {
            command.add(new NpcPlayerInFightersPool(npcPlayer));

            if (command.size() >= BATTLE_PLAYERS_COUNT) {
                return;
            }
        }
    }

    private static boolean isWaitInPoolTimeout(PcPlayerInFightersPool playerInFightersPool) {
        return System.currentTimeMillis() - playerInFightersPool.getCreateTime() > WAIT_IN_POOL_TIMEOUT;
    }
}
